﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;

namespace woaC2.SocketLib
{
    /// <summary>
    /// Sever Soket Message
    /// </summary>
    public class MessageServer : IMessageServer
    {
        /// <summary>
        /// Tcp listner - đối tượng lắng nghe mọi kết nối từ client tới
        /// </summary>
        private readonly TcpListener _listener;

        /// <summary>
        /// Biến trạng thái sever.
        /// </summary>
        private bool _isStopped;

        /// <summary>
        /// Số Lượng connect tối đa Client có thể kết nối tới 
        /// </summary>
        private ushort _maxCCU;

        public ushort MaxCCU
        {
            get {return this._maxCCU;}
            set {this._maxCCU = value;}
        }

        /// <summary>
        /// Số Client đang kết nối
        /// </summary>
        public ushort CCU
        {
            get { return (ushort)ClientRegistry.instance.Count; }
        }

        /// <summary>
        /// Khởi tạo Server Socket
        /// </summary>
        /// <param name="address"></param>
        /// <param name="port"></param>
        /// <param name="maxCCU"></param>
        public MessageServer(IPAddress address, int port, ushort maxCCU)
        {
            this._listener = new TcpListener(address, port);
            this._maxCCU = maxCCU;
            SocketLog.Write("[MessageServer.MessageServer]--[TRACE]--ServerStart at:{0}",DateTime.Now);
        }

        /// <summary>
        /// Sự kiện khi 1 client Connect
        /// </summary>
        public event EventHandler<ClientStatusChangedEventArgs> ClientConnected;

        /// <summary>
        /// Sự kiện khi 1 client Disconect
        /// </summary>
        public event EventHandler<ClientStatusChangedEventArgs> ClientDisconnected;

        /// <summary>
        /// Sự kiện khi nhận được 1 Message
        /// </summary>
        public event EventHandler<MessageRecievedEventArgs> MessageRecieved;
        
        /// <summary>
        /// Gửi mảng Byte[] tới client
        /// </summary>
        /// <param name="sessionId"></param>
        /// <param name="message"></param>
        public void SendMessageToClient(Guid sessionId, byte[] message)
        {
            Client client = ClientRegistry.instance.GetById(sessionId);

            if (client != null)
            {
                var args = new SocketAsyncEventArgs();

                List<byte> messageByteList = message.ToList();

                for (int i = 2047; i < message.Length; i += 2047)
                {
                    messageByteList.Insert(i, 0);
                    i++;
                }

                messageByteList.Add((byte)'\n');

                args.SetBuffer(messageByteList.ToArray(), 0, messageByteList.Count);

                try
                {
                    client.TcpClient.Client.SendAsync(args);
                }
                catch (Exception ex)
                {
                    ClientRegistry.instance.Remove(client.SessionID);
                    this.OnClientDisconnected(client.SessionID, client.ClientIP, client.ClientPort);
                    SocketLog.Write("[MessageServer.SendMessageToClient]--[ERROR]--" + ex);
                }
            }
        }

        public void Start()
        {
            try
            {
                this._listener.Start();
                this._listener.BeginAcceptTcpClient(this.OnAcceptTcpClient, null);
                this._isStopped = false;
            }
            catch (Exception ex)
            {
                SocketLog.Write("[MessageServer.Start]--[ERROR]--" + ex);
            }
        }

        public void Stop()
        {
            try
            {
                if (_listener != null)
                {
                    this._isStopped = true;
                    IList<Client> lsClient = ClientRegistry.instance.GetAll();
                    lsClient.AsParallel().ForAll(r =>
                        {
                            ClientRegistry.instance.Remove(r.SessionID);
                        });       
                    if (this._listener != null)
                    {
                        this._listener.Stop();
                        
                    }
                }
            }
            catch (Exception ex)
            {
                SocketLog.Write("[MessageServer.Stop]--[ERROR]--" + ex);
            }
        }

        private void BeginReceiveClientMessages(ClientMessageReceivedState clientMessageReceivedState)
        {
            try
            {
                if (clientMessageReceivedState.Client.TcpClient.Client == null) return;

                clientMessageReceivedState.Client.TcpClient.Client.BeginReceive(
                                           clientMessageReceivedState.MessagePacket,
                                           0,
                                           clientMessageReceivedState.MessagePacket.Length,
                                           SocketFlags.None,
                                           this.ClientMessageReceived,
                                           clientMessageReceivedState);
            }
            catch (Exception ex)
            {
                SocketLog.Write("[MessageServer.BeginReceiveClientMessages]--[ERROR]--" + ex);
            }
        }

        private void ClientMessageReceived(IAsyncResult ar)
        {
            var state = (ClientMessageReceivedState)ar.AsyncState;
            try
            {
                if (state.Client.TcpClient.Client == null) return;
                
                SocketError error;
                int bytesRead = state.Client.TcpClient.Client.EndReceive(ar, out error);
                if (bytesRead > 0)
                {
                    state.SerializedMessage.AddRange(state.MessagePacket.Take(bytesRead - 1));
                    if (state.MessagePacket[bytesRead - 1] > 0)
                    {
                        this.OnMessageRecieved(state.Client.SessionID, state.SerializedMessage.ToArray());
                        state.SerializedMessage.Clear();
                        this.BeginReceiveClientMessages(state);
                    }
                    else
                    {
                        ClientRegistry.instance.Remove(state.Client.SessionID);
                        this.OnClientDisconnected(state.Client.SessionID, state.Client.ClientIP, state.Client.ClientPort);
                        return;
                    }
                }
                else if (error == SocketError.Success) // Tất cả các gói tin trống đều đc coi là hack
                {
                    //remove---- thong bao den client
              
                    ClientRegistry.instance.Remove(state.Client.SessionID);
                    this.OnClientDisconnected(state.Client.SessionID, state.Client.ClientIP, state.Client.ClientPort);
                    return;
                }
            }
            catch (Exception ex)
            {
                ClientRegistry.instance.Remove(state.Client.SessionID);
                this.OnClientDisconnected(state.Client.SessionID, state.Client.ClientIP, state.Client.ClientPort);
                SocketLog.Write("[MessageServer.ClientMessageReceived]--[ERROR]--" + ex);
            }
        }

        private void OnAcceptTcpClient(IAsyncResult asyncResult)
        {
            try
            {
                if (this._isStopped)
                    return;

                if (ClientRegistry.instance.Count >= this._maxCCU)
                {
                    SocketLog.Write("[MessageServer.OnAcceptTcpClient]--[ERROR]--Limited Connect: " + this._maxCCU);
                    return;
                }
                int a = 0;
                this._listener.BeginAcceptTcpClient(this.OnAcceptTcpClient, null);

                TcpClient tcpClient = this._listener.EndAcceptTcpClient(asyncResult);

                var endPoint = (IPEndPoint)(tcpClient.Client.RemoteEndPoint);

                var client = new Client(tcpClient, endPoint.Address.ToString(),(ushort)endPoint.Port);

                this.BeginReceiveClientMessages(
                    new ClientMessageReceivedState
                        {
                            MessagePacket = new byte[2048],
                            SerializedMessage = new List<byte>(),
                            Client = client
                        });

                ClientRegistry.instance.Add(client);

                this.OnClientConnected(client.SessionID,
                                       client.ClientIP,
                                       client.ClientPort);
            }
            catch (Exception ex)
            {
                SocketLog.Write("[MessageServer.OnAcceptTcpClient]--[ERROR]--" + ex);
            }
        }

        private void OnClientConnected(Guid sessionId, string clientIP, ushort clientPort)
        {
            EventHandler<ClientStatusChangedEventArgs> handler = this.ClientConnected;
            if (handler != null)
            {
                handler(this, new ClientStatusChangedEventArgs(sessionId, clientIP, clientPort));
            }
        }

        private void OnClientDisconnected(Guid sessionId, string clientIP, ushort clientPort)
        {
            EventHandler<ClientStatusChangedEventArgs> handler = this.ClientDisconnected;
            if (handler != null)
            {
                handler(this, new ClientStatusChangedEventArgs(sessionId, clientIP, clientPort));
            }
        }

        private void OnMessageRecieved(Guid sessionId, byte[] serializedMessage)
        {
            EventHandler<MessageRecievedEventArgs> handler = this.MessageRecieved;
            if (handler != null)
            {
                handler(this, new MessageRecievedEventArgs(sessionId, serializedMessage));
            }
        }

        private class ClientMessageReceivedState
        {
            /// <summary>
            /// Client.
            /// </summary>
            public Client Client { get; set; }

            /// <summary>
            /// MessagePacket.
            /// </summary>
            public byte[] MessagePacket { get; set; }

            /// <summary>
            /// SerializedMessage
            /// </summary>
            public List<byte> SerializedMessage { get; set; }
        }
    }
}